package jmine.tec.web.servlet.rest.servlet;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jmine.tec.utils.debug.impl.DebugRuntimeFactory;
import jmine.tec.web.servlet.BeanFactoryServlet;
import jmine.tec.web.servlet.rest.RestService;

import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

/**
 * {@link BeanFactoryServlet} para servir request rest-like
 * 
 * @author takeshi
 */
public class RestLikeServlet extends BeanFactoryServlet {

    private static final long serialVersionUID = -2408389707623921168L;

    private String mappingBeanName;

    private Map<String, RestService> services;

    /**
     * {@inheritDoc}
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.mappingBeanName = this.getParameter(config, "restMapping", "restMapping");
    }

    /**
     * Devolve o servico definido pelo nome mapeado. Inicializa o mapa de rest-services caso nao esteja inicializado
     * 
     * @param mapping o nome do rest-service
     * @return {@link RestService}
     */
    protected RestService getRestService(String mapping) {
        if (this.services == null) {
            this.services = this.initServiceMapping();
        }
        return this.services.get(mapping);
    }

    /**
     * @return {@link Map}
     */
    @SuppressWarnings("unchecked")
    private Map<String, RestService> initServiceMapping() {
        try {
            return this.getBean(this.mappingBeanName, Map.class);
        } catch (NoSuchBeanDefinitionException e) {
            // autodiscovery
            if (this.getBeanFactory() instanceof ListableBeanFactory) {
                ListableBeanFactory bf = (ListableBeanFactory) this.getBeanFactory();
                String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, RestService.class);
                Map<String, RestService> servicesMap = new HashMap<String, RestService>();
                for (String beanName : names) {
                    RestService service = this.getBean(beanName, RestService.class);
                    servicesMap.put(service.getPreffix(), service);
                }
                return servicesMap;
            } else {
                throw new IllegalArgumentException("no configuration found");
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final RestService restService = this.selectService(req);
        if (restService == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else {
            boolean enabled = DebugRuntimeFactory.ENABLED;
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateCounter(this.getMBeanNameFor(restService)).increment("get");
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).start("get");
            }
            restService.get(req, resp);
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).stop("get");
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final RestService restService = this.selectService(req);
        if (restService == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else {
            boolean enabled = DebugRuntimeFactory.ENABLED;
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateCounter(this.getMBeanNameFor(restService)).increment("post");
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).start("post");
            }
            restService.post(req, resp);
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).stop("post");
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RestService restService = this.selectService(req);
        if (restService == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else {
            boolean enabled = DebugRuntimeFactory.ENABLED;
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateCounter(this.getMBeanNameFor(restService)).increment("put");
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).start("put");
            }
            restService.put(req, resp);
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).stop("put");
            }
        }
    }

    /**
     * @param request {@link HttpServletRequest}
     * @return {@link RestService}
     */
    protected RestService selectService(HttpServletRequest request) {
        final String pathInfo = request.getPathInfo();
        if (pathInfo == null || pathInfo.length() == 0) {
            return this.getRestService("");
        }
        String[] parts = pathInfo.substring(1).split("[/]");
        if (parts.length < 1) {
            return null;
        } else {
            return this.getRestService(parts[0]);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RestService restService = this.selectService(req);
        if (restService == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else {
            boolean enabled = DebugRuntimeFactory.ENABLED;
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateCounter(this.getMBeanNameFor(restService)).increment("delete");
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).start("delete");
            }
            restService.delete(req, resp);
            if (enabled) {
                DebugRuntimeFactory.getInstance().getOrCreateTimer(this.getMBeanNameFor(restService)).stop("delete");
            }
        }
    }

    /**
     * @param restService {@link RestService}
     * @return String
     */
    private String getMBeanNameFor(RestService restService) {
        return "restLikeServlet,restService=" + restService.getPreffix();
    }

}
